function FCTVPlayer() {
  // Initialize
  this.loadImages();
  this.connection_status = $('#connection-status');
  this.connection_status.append(this.images['disconnected']);
  this.login_status = $('#login-status');
  this.login_status.append(this.images['login']);
  window.addEventListener("message", $.proxy(this.onMessage, this), false);

  // Kick off server communication
  this.checkAuth();
};

FCTVPlayer.prototype.loadImages = function() {
  this.images = {};

  var image_list = [
    {
      name: 'disconnected',
      class: 'icon',
      description: 'Disconnected'
    },
    {
      name: 'connected',
      class: 'icon',
      description: 'Connected'
    },
    {
      name: 'login',
      class: 'icon',
      description: 'Sign in'
    },
    {
      name: 'logout',
      class: 'icon',
      description: 'Log out'
    },
  ];

  for (var i = 0; i < image_list.length; i++) {
    var image_info = image_list[i];
    var image = $('<img>');
    image.attr('src', '/images/' + image_info.name + '.svg');
    image.attr('title', image_info.description);
    image.addClass(image_info.class);
    this.images[image_info.name] = image;
  }
};

FCTVPlayer.prototype.sendRPC = function(url, data, success, type, delay) {
  var int_type = type || 'GET';
  
  success = $.proxy(success, this);

  function retry(xhr) {
    var int_delay = xhr.getResponseHeader('Retry-After') || Math.min(32, Math.max(2, delay || 2));
    function retryInt() {
      this.sendRPC(url, data, success, int_type, Math.pow(int_delay, 2));
    }
    console.log('RPC ' + url + ' failed. Will retry in ' + int_delay + ' seconds');
    window.setTimeout($.proxy(retryInt, this), int_delay * 1000);
  }

  headers = {};
  if (localStorage.auth_token) {
    headers['Auth-Token'] = localStorage.auth_token;
  }

  $.ajax({
    url: url,
    data: data,
    headers: headers,
    type: int_type,
    dataType: 'json',
    error: retry,
    success: function(data, stat, xhr) {
      auth_token = xhr.getResponseHeader('Auth-Token');
      if (auth_token) {
        localStorage.auth_token = auth_token;
      }
      if (data.messages) {
        // Channel-simulating shortcut
        for (var i = 0; i < data.messages.length; i++) {
          this.onChannelMessage(data.messages[i]);
        }
        delete data['messages'];
      }
      if (success) {
      	return success(data);
      }
    },
    context: this,
  });
};

FCTVPlayer.prototype.onMessage = function(e) {
  if (e.origin != window.location.origin) {
    return;
  }
  switch (e.data.type) {
  case 'logged-in':
    $('#login').css('visibility', 'hidden');
    this.checkAuth();
    break;
  }
};

FCTVPlayer.prototype.checkAuth = function() {
  this.sendRPC('/api/checkAuth', {}, this.onCheckAuth, 'POST');
};

FCTVPlayer.prototype.onCheckAuth = function(data) {
  if (data.status == 'merge') {
    this.sendRPC('/api/mergeAccounts', {
      merge_into: data.merge_into,
    }, this.onCheckAuth, 'POST');
    return;
  }
  this.createChannel();

  this.login_status.empty();
  switch (data.status) {
  case 'anonymous':
    this.login_status.append(this.images['login']);
    this.login_status.click($.proxy(this.doLogin, this));
    break;
  case 'logged-in':
    this.login_status.append(this.images['logout']);
    this.login_status.click($.proxy(this.doLogout, this));
    break;
  }
};

FCTVPlayer.prototype.doLogin = function() {
  window.location = '/user/login';
};

FCTVPlayer.prototype.doLogout = function() {
  delete localStorage['auth_token'];
  window.location = '/user/logout';
};

FCTVPlayer.prototype.createChannel = function() {
  if (this.socket) {
    // Channel will recreate itself.
    this.socket.close();
  } else {
    this.sendRPC('/api/createChannel', {}, this.onCreateChannel, 'POST');
  }
};

FCTVPlayer.prototype.onSocketOpen = function() {
  console.log('Channel opened')
  this.connection_status.empty();
  this.connection_status.append(this.images['connected']);
};

FCTVPlayer.prototype.onSocketClose = function() {
  if (!this.socket) {
    return;
  }
  console.log('Channel closed')
  this.connection_status.empty();
  this.connection_status.append(this.images['disconnected']);
  this.socket = null;
  this.createChannel();
};

FCTVPlayer.prototype.onSocketMessage = function(msg) {
  this.onChannelMessage(JSON.parse(msg.data));
}

FCTVPlayer.prototype.onChannelMessage = function(msg) {
  switch (msg.type) {
  case 'reconnect':
    this.socket.close();
    break;
  case 'media-add':
    $('channel > region').append(
      $('<media>')
        .append($('<media_title>').text(msg.value.title))
        .append($('<img>').attr('src', 'https://i.ytimg.com/vi/' + msg.value.source_id + '/sddefault.jpg'))
      );
    break;
  default:
    console.log(msg);
    break;
  }
};

FCTVPlayer.prototype.onSocketError = function(msg) {
  console.log(msg);
  this.socket.close();
};

FCTVPlayer.prototype.onCreateChannel = function(data) {
  channel = new goog.appengine.Channel(data['channel-token']);
  this.socket = channel.open({
    onopen: $.proxy(this.onSocketOpen, this),
    onclose: $.proxy(this.onSocketClose, this),
    onmessage: $.proxy(this.onSocketMessage, this),
    onerror: $.proxy(this.onSocketError, this),
  });
};


$(document).ready(function() {
  new FCTVPlayer();
});
